#!/bin/bash
#
# Copyright (c) 2013, Duke Fong <duke@dukelec.com>
#
# All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation version 2 of
# the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307 USA
#

# Reset
Color_Off='\e[0m'       # Text Reset

# Regular Colors
Black='\e[0;30m'        # Black
Red='\e[0;31m'          # Red
Green='\e[0;32m'        # Green
Yellow='\e[0;33m'       # Yellow
Blue='\e[0;34m'         # Blue
Purple='\e[0;35m'       # Purple
Cyan='\e[0;36m'         # Cyan
White='\e[0;37m'        # White

# High Intensity
IBlack='\e[0;90m'       # Black
IRed='\e[0;91m'         # Red
IGreen='\e[0;92m'       # Green
IYellow='\e[0;93m'      # Yellow
IBlue='\e[0;94m'        # Blue
IPurple='\e[0;95m'      # Purple
ICyan='\e[0;96m'        # Cyan
IWhite='\e[0;97m'       # White

# Bold High Intensity
BIBlack='\e[1;90m'      # Black
BIRed='\e[1;91m'        # Red
BIGreen='\e[1;92m'      # Green
BIYellow='\e[1;93m'     # Yellow
BIBlue='\e[1;94m'       # Blue
BIPurple='\e[1;95m'     # Purple
BICyan='\e[1;96m'       # Cyan
BIWhite='\e[1;97m'      # White

# xml_forall

read_dom () {
    local IFS=\>
    read -d \< ENTITY CONTENT
    local RET=$?
    TAG_NAME=${ENTITY%% *}
    ATTRIBUTES=${ENTITY#* }
    return $RET
}

parse_remove_dom () {
    if [[ $TAG_NAME = "remove-project" ]] ; then
        eval local ${ATTRIBUTES%/}
        echo $name
    fi
}

parse_dom () {
    if [[ $TAG_NAME = "project" ]] ; then
        eval local ${ATTRIBUTES%/}

        for i in $REMOVED_LIST; do
            if [ x$i == x$name ]; then return; fi
        done

        # echo "$path --> $name --> $groups"

        if [ $is_normal_user == "true" ]; then
            cd $path
        else
            cd $name.git
        fi

        if [ ! -z $revision ]; then
            local REPO_RREV=$revision
        else
            local REPO_RREV=$DEFAULT_RREV
        fi

        if [ ! -z $remote ]; then
            local REPO_REMOTE=$remote
        else
            local REPO_REMOTE=$DEFAULT_REMOTE
        fi

        if [[ $is_normal_user == "true" && ! $REPO_RREV =~ "tags" ]]; then
            local REPO_LREV=$(git show-ref -s $REPO_REMOTE/$REPO_RREV)
        else
            # skip which contain "$remote"
            local REPO_LREV=$(git rev-list -1 $REPO_RREV)
        fi

        local REPO_PROJECT=$name
        local REPO_PATH=$path

        eval "$1"
        cd "$TOP_DIR"
    fi
}

format_xml () {
    xmllint --format "$1" | sed '/<!--.*-->/d' | sed '/<!--/,/-->/d'
}

join_xml () {
    if [ -f .repo/local_manifest.xml ]; then
        format_xml .repo/local_manifest.xml >> ~/.repo-annex/all.xml
    elif [ -d .repo/local_manifests ]; then
        for i in .repo/local_manifests/*.xml; do
            format_xml $i  >> ~/.repo-annex/all.xml
        done
    fi
    format_xml .repo/manifest.xml >> ~/.repo-annex/all.xml
    if [ $is_normal_user == "false" ]; then
        echo "<manifest>" >> ~/.repo-annex/all.xml
        echo "  <project name=\"$manifest_file\"/>" >> ~/.repo-annex/all.xml
        echo "</manifest>" >> ~/.repo-annex/all.xml
    fi
}

xml_forall () {
    # $1: command; $2, $3, $4 ... : user args

    echo "" > ~/.repo-annex/all.xml
    global_init
    join_xml

    # default revision name
    DEFAULT_RREV=$(xmllint --xpath "string(//manifest/default/@revision)" .repo/manifest.xml)
    DEFAULT_REMOTE=$(xmllint --xpath "string(//manifest/default/@remote)" .repo/manifest.xml)

    REMOVED_LIST=`cat ~/.repo-annex/all.xml | while read_dom; do parse_remove_dom; done`

    cat ~/.repo-annex/all.xml | while read_dom; do parse_dom "$@"; done
}

#

function format_path() {
    local path=`readlink -m "/$1"`
    echo "${path/\//}"
}
function url_get_path() {

    local proto="$(echo $1 | grep :// | sed -e's,^\(.*://\).*,\1,g')"
    if [ -z $proto ]; then
        echo "$1"
    else
        local url="$(echo ${1/$proto/})"
        echo "$(echo $url | grep / | cut -d/ -f2-)"
    fi
}
function url_get_server() {
    local proto="$(echo $1 | grep :// | sed -e's,^\(.*://\).*,\1,g')"
    if [ ! -z $proto ]; then
        local url="$(echo ${1/$proto/})"
        local host="${url%%/*}"
        echo "$proto$host/"
    fi
}

#

function repo_exist_test () {
    if [ ! -d .repo ]; then
        echo -e "\nCan't find .repo in current dir!"
        exit
    fi
}

#

function get_previous_rev () {
    # $1: branch-name, $2: current-rev

    # git 1.7.4.2 or newer & may get more than one parents
    #END=`git rev-list --max-parents=0 $BRANCH`
    END=`git rev-list $1 | tail -n 1`
    if [ "$2" == "$END" ]; then
        exit -1
    else
        git rev-parse $2~1
    fi
}
export -f get_previous_rev

function to_timestamp () {
    if [ -z $1 ]; then
        echo "0"
    else
        echo $(git show -s --format=%ct $1)
    fi
}
export -f to_timestamp

function get_rev_by_timestamp () {
    # $1: branch-name, $2: datestamp
    # TODO: check git empty

    CURRENT=`git rev-parse $1`
    CURRENT_T=`to_timestamp $CURRENT`

    while (( $CURRENT_T > $2 )); do
        CURRENT=`get_previous_rev $1 $CURRENT`
        if [ -z $CURRENT ]; then exit -1; fi
        CURRENT_T=`to_timestamp $CURRENT`
    done

    echo "$CURRENT"
}
export -f get_rev_by_timestamp

function get_rev_by_time () {
    # $1: branch-name, $2: date, e.g. "2012-11-01 21:28:42"
    get_rev_by_timestamp $1 `date -d "$2" +%s`
}
export -f get_rev_by_time

function show_log_by_time() {
    # $1: date, e.g. "2012-11-01 21:28:42", $2: depth

    echo "making temporary dir"
    repo_exist_test
    xml_forall 'mkdir -p .git/.repo-annex'

    echo "scaning"
    if [ -z $2 ]; then
        DEPTH=8
    else
        DEPTH=$2
    fi

    START_T=`date -d "$1" +%s`
    export START_T

    echo "0" > ~/.repo-annex/max_t
    xml_forall '
            CURRENT=$(get_rev_by_timestamp $REPO_LREV $START_T)
            to_timestamp $CURRENT > .git/.repo-annex/current_t
            echo $CURRENT > .git/.repo-annex/current
    '

    echo "show result"

    while (( $DEPTH > 0)); do
        DEPTH=$(($DEPTH - 1))

        echo -------------------------------$DEPTH-------------------------------

        xml_forall '
            CURRENT_T=$(cat .git/.repo-annex/current_t)
            if (( $CURRENT_T > $(cat ~/.repo-annex/max_t) )); then
                echo $CURRENT_T > ~/.repo-annex/max_t
            fi
        '

        #echo "get result, max = $(cat ~/.repo-annex/max_t)"
        out=`xml_forall '
            CURRENT_T=$(cat .git/.repo-annex/current_t)
            if (( $CURRENT_T == $(cat ~/.repo-annex/max_t) )); then
                pwd

                CURRENT=$(cat .git/.repo-annex/current)
                git log --name-status $CURRENT^..$CURRENT

                #echo \"current_t = $CURRENT_T, max = $(cat ~/.repo-annex/max_t)\"

                echo 0 > ~/.repo-annex/max_t

                CURRENT=$(get_previous_rev $REPO_LREV $CURRENT)
                CURRENT_T=$(to_timestamp $CURRENT)
                echo $CURRENT_T > .git/.repo-annex/current_t
                echo $CURRENT > .git/.repo-annex/current
                exit
            fi
        '`
        echo -e "$out\n"
    done

    echo "deleting temporary dir"
    xml_forall 'rm -rf .git/.repo-annex'
}

function checkout_by_time() {
    # $1: date, e.g. "2012-11-01 21:28:42"

    echo "scaning"
    repo_exist_test

    START_T=`date -d "$1" +%s`
    export START_T

    echo "0" > ~/.repo-annex/max_t
    xml_forall '
            echo -----------------------------------------------------------
            pwd
            CURRENT=$(get_rev_by_timestamp $REPO_LREV $START_T)
            if [ -z $CURRENT ]; then
                echo -e "Not found, removing working tree ...\n"
                rm -rf *
                exit;
            fi
            git checkout $CURRENT > /dev/null
            git reset --hard $CURRENT > /dev/null
            git clean -dxf > /dev/null
            #git log --name-status $CURRENT^..$CURRENT > ~/.repo-annex/log
            #cat ~/.repo-annex/log
            #echo -e "\n"
    '

    #echo -e "\nShow the working tree status:\n"
    #repo status
}

function generate_patch_from_time() {
    # $1: date, e.g. "2012-11-01 21:28:42"

    echo "scaning"
    repo_exist_test

    START_T=`date -d "$1" +%s`
    export START_T

    echo "0" > ~/.repo-annex/max_t
    xml_forall '
            CURRENT=$(get_rev_by_timestamp $REPO_LREV $START_T)
            if [ -z $CURRENT ]; then
                echo -e "Not found ...\n"
                exit;
            fi
            rm -rf *.patch
            if [ "$CURRENT" != "$REPO_LREV" ]; then
                echo -----------------------------------------------------------
                pwd
                git format-patch $CURRENT..$REPO_LREV
                mkdir -p "$2/$REPO_PATH"
                mv *.patch "$2/$REPO_PATH/"
            fi
    ' "$2"
}

function show_working_tree_last_commit() {

    echo "making temporary dir"
    repo_exist_test
    xml_forall 'mkdir -p .git/.repo-annex'

    echo "scaning"

    echo "0" > ~/.repo-annex/max_t
    xml_forall '
            CURRENT=$(git rev-parse HEAD)
            to_timestamp $CURRENT > .git/.repo-annex/current_t
    '

    echo "show result"


    echo -------------------------------$DEPTH-------------------------------

    xml_forall '
        CURRENT_T=$(cat .git/.repo-annex/current_t)
        if (( $CURRENT_T > $(cat ~/.repo-annex/max_t) )); then
            echo $CURRENT_T > ~/.repo-annex/max_t
        fi
    '

    #echo "get result, max = $(cat ~/.repo-annex/max_t)"
    out=`xml_forall '
        CURRENT_T=$(cat .git/.repo-annex/current_t)
        if (( $CURRENT_T == $(cat ~/.repo-annex/max_t) )); then
            pwd
            git log --name-status HEAD^..HEAD
            exit
        fi
    '`
    echo -e "$out\n"


    echo "deleting temporary dir"
    xml_forall 'rm -rf .git/.repo-annex'
}


# exist test

function check_exist () {

    echo "Start work...."
    repo_exist_test

    REPO_DIR=`pwd`
    REPO_LIST=`xml_forall 'pwd'`

    echo "find *.git:"

    # TODO: checking the real working tree
    GIT_LIST=`find $REPO_DIR/.repo/projects -name "*.git"`

    echo "comparing:"
    echo ""

    for i in $GIT_LIST; do
      i=${i##$REPO_DIR/.repo/projects/}
      i=${i%%.git}

      is_found='no'
      for p in $REPO_LIST; do
        p=${p##$REPO_DIR/}
        if [ $i == $p ]; then
          is_found='yes'
        fi
      done

      if [ $is_found == 'no' ]; then
        if [ "${i%%/*}" != ".repo" ]; then
          echo "not found --> $i"
          if [ x"$1" == x"-d" ]; then
            rm -rf .repo/projects/$i.git
            rm -rf $i
            echo "removed."
          fi
        fi
      fi
    done
}

function check_exist_mirror () {

    echo "Start work...."
    repo_exist_test
    REPO_LIST=`xml_forall 'echo $REPO_PROJECT'`

    echo "find *?.git:"
    GIT_LIST=`find . -name "*?.git"`

    echo "comparing:"
    echo ""

    for i in $GIT_LIST; do
      i=${i##./}
      i=${i%%.git}

      is_found="no"
      for p in $REPO_LIST; do

        if [ $i == $p ]; then
          is_found="yes"
        fi
      done

      if [ "$is_found" == "no" ]; then
        if [ "${i%%/*}" != ".repo" ]; then
          echo "not found --> $i"
          if [ x"$1" == x"-d" ]; then
            rm -rf $i.git
            echo "removed."
          fi
        fi
      fi
    done
}


# change url

function change_url () {
    local old_url="$manifest_url_server$default_url_path"
    echo "Old url: $old_url"
    local new_url

    read -e -p "Please input new : " -i "$old_url" new_url

    manifest_url_server=`url_get_server "$new_url"`

    local repo_url=`cat .repo/repo/.git/config | grep url | cut -d\  -f3-`
    local repo_path="${repo_url#$old_url}"

    if [ "$repo_url" != "$repo_path" ]; then
        echo "updating .repo/repo ..."
        cd .repo/repo
        repo_url="$new_url$repo_path"
        read -e -p "Please confirm: " -i "$repo_url" repo_url
        git remote set-url origin "$repo_url"
        cd - > /dev/null
    else
        echo "repo url = $repo_url"
    fi

    echo "updating .repo/manifests ..."
    cd .repo/manifests
    git remote set-url origin "$new_url/$manifest_file"
    cd - > /dev/null

    echo "updating all ..."
    xml_forall '
        if [ $REPO_REMOTE != $DEFAULT_REMOTE ]; then
            local remote_fetch=$(xmllint --xpath "string(//manifest/remote[@name='\''$REPO_REMOTE'\'']/@fetch)" $TOP_DIR/.repo/manifest.xml)
            local url_path=`format_path $manifest_url_path/$remote_fetch`
            local new_url="$manifest_url_server$url_path"
            new_url=${new_url%/} # avoid duplicate "/" when $url_path is empty
        fi
        git remote set-url $REPO_REMOTE $new_url/$REPO_PROJECT
    '
}

# change branch

function change_branch () {
    cd .repo/manifests
    CURRENT_B=`git config --get branch.default.merge`

    BRANCH_LIST=`git branch  -a | grep remotes/origin`
    i=0
    echo -e "Please select a branch by num:\n"
    for item in $BRANCH_LIST; do
        item=${item##remotes/origin/}
        BRANCH_ARRAY[$i]=$item
        echo -n -e "$i"
        if [ x"$CURRENT_B" == x"$item" ]; then
            select_i=$i
            echo -n -e " ${BIRed}*${Color_Off}"
        fi
        echo -n -e "\t$item\n"

        i=$((i + 1))
    done

    echo ""
    read -e -p "Your choose is: " -i "$select_i" select_i
    echo "Seting default branch to \"${BRANCH_ARRAY[$select_i]}\""
    git config branch.default.merge ${BRANCH_ARRAY[$select_i]}
    git reset --hard origin/${BRANCH_ARRAY[$select_i]}

    CURRENT_HASH=`sha1sum < ../manifest.xml`
    i=0
    echo -e "\nPlease select xml file by num:\n"
    for item in *.xml; do
        XML_ARRAY[$i]="$item"
        echo -n -e "$i"
        if [ x"$CURRENT_HASH" == x"`sha1sum < \"$item\"`" ]; then
            select_i=$i
            echo -n -e " ${BIRed}*${Color_Off}"
        fi
        echo -n -e "\t$item\n"
        i=$((i + 1))
    done

    echo ""
    read -e -p "Your choose is: " -i "$select_i" select_i
    echo "Seting default xml to \"${XML_ARRAY[$select_i]}\""
    cd ..
    rm manifest.xml
    ln -s "manifests/${XML_ARRAY[$select_i]}" manifest.xml
    cd .. # return to top

    repo_exist_test

    echo ""
    while true; do
    read -p "Do you want to show obsolete projects? " yn
    case $yn in
        [Yy]* ) check_exist; break;;
        [Nn]* ) break;;
        * ) echo "Please answer yes or no.";;
    esac
    done

    echo ""
    while true; do
    read -p "Do you want to delete obsolete projects? " yn
    case $yn in
        [Yy]* ) check_exist -d; break;;
        [Nn]* ) break;;
        * ) echo "Please answer yes or no.";;
    esac
    done

    echo ""
    while true; do
    read -p "Do you want to sync now? " yn
    case $yn in
        [Yy]* ) repo sync; break;;
        [Nn]* ) break;;
        * ) echo "Please answer yes or no.";;
    esac
    done
}

#

rm -rf ~/.repo-annex
mkdir ~/.repo-annex

global_init () {
    TOP_DIR="`pwd`"

    is_normal_user="true"
    if [ ! -d .repo/projects ]; then
        is_normal_user="false"
    fi

    manifest_url=`cat .repo/manifests/.git/config | grep url | cut -d\  -f3-`
    manifest_url_file=`url_get_path "$manifest_url"`
    manifest_url_path=`format_path $manifest_url_file/..`
    manifest_url_server=`url_get_server "$manifest_url"`

    default_remote=$(xmllint --xpath "string(//manifest/default/@remote)" .repo/manifest.xml)
    default_remote_fetch=$(xmllint --xpath "string(//manifest/remote[@name='$default_remote']/@fetch)" .repo/manifest.xml)

    default_url_path=`format_path $manifest_url_path/$default_remote_fetch`
    manifest_file=${manifest_url_file/$default_url_path\//}
    manifest_file=${manifest_file%.git}
}

global_init

if [ x"$1" == x"checkout" ]; then
    checkout_by_time "$2"
elif [ x"$1" == x"generate-patch" ]; then
    generate_patch_from_time "$2" "$3"
elif [ x"$1" == x"last" ]; then
    show_working_tree_last_commit
elif [ x"$1" == x"history" ]; then
    show_log_by_time "$2" $3
elif [ x"$1" == x"exist" ]; then
    if [ $is_normal_user == "true" ]; then
        check_exist $2
    else
        check_exist_mirror $2
    fi
elif [ x"$1" == x"change-url" ]; then
    change_url
elif [ x"$1" == x"change-branch" ]; then
    change_branch
elif [ x"$1" == x"set-push" ]; then
    repo_exist_test
    xml_forall 'git config remote.$REPO_REMOTE.push HEAD:refs/for/refs/heads/$REPO_RREV'
    echo "Done."
elif [ x"$1" == x"prune" ]; then
    repo_exist_test
    xml_forall 'echo "${REPO_PROJECT} :"; git fetch -p $REPO_REMOTE;'
    echo "./.repo/manifests :"
    cd .repo/manifests
    git fetch -p
else
    echo -e "Usage:\n"

    echo -e "repo-annex.sh checkout \"TIME\"\n\t"                          \
        "\"TIME\" format \"2013-11-01 14:35:59\"\n"

    echo -e "repo-annex.sh generate-patch \"TIME\" absolute_out_path\n\t"  \
        "generate patch from date\n"

    echo -e "repo-annex.sh last\n\t"                                       \
        "show last commit in working tree\n"

    echo -e "repo-annex.sh history \"TIME\" [DEPTH]\n\t"                   \
        "cross project log display by date\n\t"                            \
        "default DEPTH is 8\n"

    echo -e "repo-annex.sh exist [-d]\n\t"                                 \
        "show obsolete projects (not list in the manifest.xml)\n\t"        \
        "-d : delete or not\n"

    echo -e "repo-annex.sh change-url\n\t"                                 \
        "change remote url\n"

    echo -e "repo-annex.sh change-branch\n\t"                              \
        "change repo default branch\n"

    echo -e "repo-annex.sh set-push\n\t"                                   \
        "set git default push HEAD to gerrit\n"

    echo -e "repo-annex.sh prune\n\t"                                      \
        "prune non-existent remote branch\n"

    echo -e "xml_forall\n\t"                                               \
        "source repo-annex.sh\n\t"                                         \
        "xml_forall 'COMMAND1; COMMAND2 \$2' var2 var3 ...\n\t"            \
        "variables named the same as the 'repo forall':\n\t"               \
        "\t\$REPO_REMOTE \$REPO_PROJECT \$REPO_PATH \$REPO_RREV \$REPO_LREV\n"

    echo "repo-annex ver 0.5"
fi
